package com.nf.mvc;

import com.nf.mvc.adapter.HttpRequestHandlerAdapter;
import com.nf.mvc.adapter.RequestMappingHandlerAdapter;
import com.nf.mvc.argument.*;
import com.nf.mvc.cors.CorsConfiguration;
import com.nf.mvc.cors.CorsProcessor;
import com.nf.mvc.cors.DefaultCorsProcessor;
import com.nf.mvc.exception.ExceptionHandlerExceptionResolver;
import com.nf.mvc.exception.LogHandlerExceptionResolver;
import com.nf.mvc.exception.ParameterizedExceptionHandlersExceptionResolver;
import com.nf.mvc.exception.PrintStackTraceHandlerExceptionResolver;
import com.nf.mvc.mapping.NameConventionHandlerMapping;
import com.nf.mvc.mapping.RequestMappingHandlerMapping;
import com.nf.mvc.support.Delimiters;
import com.nf.mvc.util.ScanUtils;
import com.nf.mvc.util.StringUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;

/**
 * 此类是一个前端控制器，我也喜欢称其为总控器，此Servlet类是请求进入到我们的mvc框架的入口部分
 * <h3>基本使用</h3>
 * <p>
 * 用户通常应该只配置一个这样的Servlet，并且为其url-pattern设置值为"/",虽然理论上你可以配置多个这样的servlet，但很少见。
 * 而为了让其支持文件上传，通常也要配置multipart-config，如果想让servlet容器启动时就执行DispatcherServlet的初始化逻辑，
 * 通常也会配置load-on-startup选项,典型的配置如下:
 * <pre class="xml">
 *
 *   &lt;servlet>
 *      &lt;servlet-name>mvcDemo< /servlet-name>
 *      &lt;servlet-class>com.nf.mvc.DispatcherServlet&lt;/servlet-class>
 *      &lt;init-param>
 *          &lt;param-name>base-package&lt;/param-name>
 *          &lt;param-value>mvcDemo.web&lt;/param-value>
 *      &lt;/init-param>
 *      &lt;load-on-startup>200&lt;/load-on-startup>
 *      &lt;multipart-config>
 *      &lt;/multipart-config>
 *  &lt;/servlet>
 *
 *  &lt;servlet-mapping>
 *      &lt;servlet-name>mvcDemo&lt;/servlet-name>
 *      &lt;url-pattern>/&lt;/url-pattern>
 *  &lt;/servlet-mapping>
 * </pre>
 * </p>
 * <h3>默认组件与自定义组件</h3>
 * <p>mvc框架的组件分为框架提供的默认组件与用户提供的自定义组件，自定义组件的优先级总是高于同类型的框架提供的的默认组件的，
 * 同类型的自定义组件的优先级可以通过Order注解调整</p>
 * <p>整个Mvc框架管理有如下7大组件,其中前4大组件的扩展只要编写相应接口的实现类并放置在框架可以扫描到的包里面即可，
 * 不需要再在别的地方进行注册或进行额外的配置。</p>
 * <p>第五大组件ViewResult,用户通常采用继承此类的方式来扩展Mvc框架的能力,接着把新的ViewResult类型应用在控制器方法
 * 或者异常解析方法的返回类型上即可，并不需要被框架扫描到，更不需要进行额外的注册处理。</p>
 * <p>第6类组件是用来对Mvc框架提供的5大内置组件(HandlerMapping,HandlerAdapter,MethodArgumentResolver,
 * HandlerExceptionResolver,CorsConfiguration)进行定制配置用的,以便可以修改内置组件的某些默认行为</p>
 * <p>最后一个是拦截器组件，是用户用来编写项目的拦截相关的业务使用，比如实现验证方面的拦截器</p>
 * <ul>
 *     <li>{@link HandlerMapping}</li>
 *     <li>{@link HandlerAdapter}</li>
 *     <li>{@link MethodArgumentResolver}</li>
 *     <li>{@link HandlerExceptionResolver}</li>
 *     <li>{@link ViewResult}</li>
 *     <li>{@link MvcConfigurer}</li>
 *     <li>{@link HandlerInterceptor}</li>
 * </ul>
 * </p>
 * <h3>组件的获取</h3>
 * <p>
 * 用户自定义前4大组件是通过类扫描的方式获取的，用户在配置DispatcherServlet的时候通过参数<i>{@code base-package }</i>指定扫描的基础包，
 * 框架会扫描指定包及其子包下的所有类型，并加载到jvm。所以，强烈建议指定的包，只包含web层面的一些组件，不要指定dao，service相关的类所在的包，
 * web层面的类型主要有如下一些类型
 *    <ul>
 *       <li>用户创建的后端控制器</li>
 *       <li>用户创建的拦截器</li>
 *       <li>用户创建的WebMvcConfigurer</li>
 *       <li>用户对前4大组件的扩展实现</li>
 *    </ul>
 * </p>
 * <h3>核心组件的实例化</h3>
 * <p>
 *   核心的mvc框架组件都是在此类的{@link #init(ServletConfig)}方法实例化并完成组合的。可以看任何一个以init开头的方法了解详情，
 *   比如{@link #initHandlerMappings()},7大被Mvc框架处理的组件都要求必须有默认构造函数
 * </p>
 * <h3>初始化处理流程</h3>
 * <ul>
 *     <li>扫描指定包及其子包下的所有类型</li>
 *     <li>创建MvcContext实例</li>
 *     <li>初始化Mvc框架
 *          <ol>
 *              <li>初始化所有的参数解析器</li>
 *              <li>初始化所有的HandlerMapping</li>
 *              <li>初始化所有的HandlerAdapters</li>
 *              <li>初始化所有的异常解析器</li>
 *          </ol>
 *     </li>
 *     <li>配置Mvc框架：利用{@link MvcConfigurer}的实现类对Mvc框架内部组件进行配置</li>
 * </ul>
 * <h3>请求处理流程</h3>
 * <ol>
 *     <li>用户发起请求</li>
 *     <li>遍历所有的HandlerMapping，直到找到一个HandlerExecutionChain来处理请求，找不到就交给容器的<i>默认Servlet</i>处理请求</li>
 *     <li>处理拦截器链的前置逻辑</li>
 *     <li>遍历所有的HandlerAdapter，直到找到一个支持此Handler的HandlerAdapter，找不到就抛异常</li>
 *     <li>HandlerAdapter开始负责Handler方法的调用执行
 *          <ol>
 *              <li>获取Handler类型的实例化</li>
 *              <li>遍历方法的每一个参数，解析出此参数的值，解析的时候是遍历每一个参数解析器，找到能支持的解析器就结束遍历，并利用解析器解析出值，
 *              如果找不到能解析的解析就抛出异常，具体见{@link MethodArgumentResolverComposite#resolveArgument(MethodParameter, HttpServletRequest)} </li>
 *              <li>执行Handler的方法</li>
 *              <li>适配Handler执行结果为ViewResult类型</li>
 *          </ol>
 *     </li>
 *     <li>处理拦截器链的后置逻辑</li>
 *     <li>对Handler的执行结果ViewResult进行渲染（render）</li>
 *     <li>如果执行链出了异常交给异常解析器链去处理</li>
 * </ol>
 * <h3>静态资源处理</h3>
 * <p>
 *     静态资源的地址如果没有对应HandlerMapping能处理，就进入到了默认Servlet的处理逻辑，
 *     而默认Servlet会直接读取静态资源并响应给请求端
 * </p>
 *
 *  <h3>cors处理</h3>
 *  <p>
 *  mvc框架只实现了全局跨域的处理，并没有对某个地址进行单独的跨域处理，所以，跨域的配置是影响到所有的url请求的，
 *  如果你没有实现{@link MvcConfigurer#configureCors(CorsConfiguration)}方法进行跨域的定制配置，
 *  那么它会采用默认值，具体的跨域配置情况见{@link CorsConfiguration#applyDefaultConfiguration()}
 *  </p>
 *
 * @see MvcContext
 * @see MethodArgumentResolver
 * @see HandlerMapping
 * @see HandlerAdapter
 * @see HandlerExecutionChain
 * @see HandlerExceptionResolver
 * @see MvcConfigurer
 * @see CorsConfiguration
 */
public class DispatcherServlet extends HttpServlet {
    /**
     * 此选项是用来配置要扫描的类所在的基础包的，在DispatcherServlet的init-param里面进行配置
     */
    private static final String BASE_PACKAGE = "base-package";
    private final List<HandlerMapping> handlerMappings = new ArrayList<>();
    private final List<HandlerAdapter> handlerAdapters = new ArrayList<>();
    private final List<MethodArgumentResolver> argumentResolvers = new ArrayList<>();
    private final List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>();

    /**
     * 用这种方式实例化是因为其调用了applyDefaultConfiguration方法，创建出来的对象是有了一些默认跨越设置的
     * 因为我现在的策略是：不管有没有配置器对跨域进行定制配置，都要全局进行跨域支持的处理
     */
    private final CorsConfiguration corsConfiguration = CorsConfiguration.defaultInstance();

    // region 初始化逻辑

    /**
     * 此方法完成mvc框架中的初始化逻辑，主要完成了如下工作
     * <ul>
     *     <li>扫描指定包下所有的类,但主要关注的是如下类型：
     *          <ul>
     *              <li>HandlerMapping</li>
     *              <li>MethodArgumentResolver</li>
     *              <li>HandlerAdapter</li>
     *              <li>HandlerExceptionResolver</li>
     *              <li>WebMvcConfigurer</li>
     *              <li>HandlerInterceptor</li>
     *              <li>Handler(可能是任何类型），mvc框架现在只支持方法有@RequestMapping修饰的类型</li>
     *          </ul>
     *     </li>
     *     <li>初始化MvcContext</li>
     *     <li>初始化Mvc框架核心组件</li>
     *     <li>配置Mvc框架提供的默认组件</li>
     * </ul>
     * <p>
     *     注意：初始化完毕之后，扫描的7大类型中，除了Handler其它6大类型全部已经被实例化了，
     *  并且也是在初始化阶段完成了4大核心组件的组装
     * </p>
     * <h3>关于单例与原型</h3>
     * <p>
     * 单例对象：只有一个实例，原型对象：每次都会实例化一个对象出来。<br/>
     * 所有在DispatcherServlet类的初始化时创建出来的对象基本都是单例的，也就是只会实例化一个对象，主要的单例对象如下
     *     <ol>
     *        <li>DispatcherServlet</li>
     *        <li>所有的HandlerMapping，包含默认与定制</li>
     *        <li>所有的HandlerAdapter，包含默认与定制</li>
     *        <li>所有的MethodArgumentResolver，包含默认与定制</li>
     *        <li>所有的HandlerExceptionResolver，包含默认与定制</li>
     *        <li>所有的定制拦截器HandlerInterceptor，mvc框架没有提供默认的拦截器实现</li>
     *     </ol>
     *     常见的原型对象有：
     *     <ol>
     *         <li>用户控制器类(用户编写)</li>
     *         <li>控制器方法的复杂类型的参数(用户编写)</li>
     *         <li>ViewResult对象(用户编写)</li>
     *         <li>MethodParameter</li>
     *         <li>HandlerMethod</li>
     *     </ol>
     * </p>
     *
     * @param config ServletConfig对象
     * @throws ServletException ServletException对象
     */
    @Override
    public final void init(ServletConfig config) throws ServletException {
        initMvcContext(config);
        initMvc();
        configMvcComponents();
    }

    /**
     * 扫描指定包下的所有类型并分类整理好
     *
     * @param config ServletConfig
     */
    protected void initMvcContext(ServletConfig config) {
        // 获取要扫描的类所在的基础包的名字,支持用分隔符指定多个要扫描的包
        String[] basePackages = getBasePackages(config);
        Set<Class<?>> allScannedClasses = ScanUtils.scan(basePackages);
        MvcContext.getMvcContext().resolveScannedResult(allScannedClasses);
    }

    /**
     * 合并用户定制的mvc组件与框架默认提供的mvc组件
     */
    protected void initMvc() {
        // 参数解析器因为被Adapter与异常解析器使用，所以其初始化要在这两类组件之前进行
        // HandlerMapping组件的初始化没有顺序要求
        initHandlerMappings();
        initArgumentResolvers();
        initHandlerAdapters();
        initExceptionResolvers();
    }

    /**
     * 对Mvc框架的核心组件进行配置,当前实现只对内部提供的默认组件进行配置.
     * <p>为什么方法名不叫configDefaultMvcComponents,原因有二
     * <ul>
     *     <li>其它的配置方法的名字中都没有加default</li>
     *     <li>子类如果重写此方法是可以改变只配置默认组件的行为的,
     *     可以改成对所有组件进行配置,虽然必要性不是很大</li>
     * </ul>
     * </p>
     */
    protected void configMvcComponents() {
        MvcContext mvcContext = MvcContext.getMvcContext();
        MvcConfigurer mvcConfigurer = mvcContext.getMvcConfigurer();
        // 没有配置器，表明不需要对mvc框架内置的组件进行配置
        if (mvcConfigurer == null) {
            return;
        }

        // 由于这些配置方法子类可以重写，所以给这些方法都设置了参数，这样子类就可以直接利用这些参数获取一些信息，
        // 而不用在本类中提供相应的getter方法或者把字段设置为protected。不考虑重写的话，这些方法是可以不用添加任何参数的
        // 只需要给mvc框架提供的默认组件提供配置，用户提供的自定义组件无需配置，用户更改源码就可以了
        configArgumentResolvers(getDefaultArgumentResolvers(), mvcConfigurer);
        configHandlerMappings(getDefaultHandlerMappings(), mvcConfigurer);
        configHandlerAdapters(getDefaultHandlerAdapters(), mvcConfigurer);
        configExceptionResolvers(getDefaultExceptionResolvers(), mvcConfigurer);
        // 由于corsConfiguration对象是有了默认值设置的实例，没有配置器的时候不配置cors也能用默认设置处理跨域
        configGlobalCors(this.corsConfiguration, mvcConfigurer);
    }

    protected void configArgumentResolvers(List<MethodArgumentResolver> argumentResolvers, MvcConfigurer mvcConfigurer) {
        configMvcComponents(argumentResolvers, mvcConfigurer::configureArgumentResolver);
    }

    protected void configHandlerMappings(List<HandlerMapping> handlerMappings, MvcConfigurer mvcConfigurer) {
        configMvcComponents(handlerMappings, mvcConfigurer::configureHandlerMapping);
    }

    protected void configHandlerAdapters(List<HandlerAdapter> handlerAdapters, MvcConfigurer mvcConfigurer) {
        configMvcComponents(handlerAdapters, mvcConfigurer::configureHandlerAdapter);
    }

    protected void configExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers, MvcConfigurer mvcConfigurer) {
        configMvcComponents(exceptionResolvers, mvcConfigurer::configureExceptionResolver);
    }

    protected void configGlobalCors(CorsConfiguration configuration, MvcConfigurer mvcConfigurer) {
        // 下面这行代码的作用等价于mvcConfigurer.configureCors(configuration);下面这种写法是为了与其它组件配置写法一致
        configMvcComponents(Collections.singletonList(configuration), mvcConfigurer::configureCors);
    }

    private <T> void configMvcComponents(List<T> mvcComponents, Consumer<T> consumer) {
        mvcComponents.forEach(consumer);
    }

    private void initArgumentResolvers() {
        List<MethodArgumentResolver> customArgumentResolvers = getCustomArgumentResolvers();
        List<MethodArgumentResolver> defaultArgumentResolvers = getDefaultArgumentResolvers();

        argumentResolvers.addAll(customArgumentResolvers);
        argumentResolvers.addAll(defaultArgumentResolvers);

        MvcContext.getMvcContext().setArgumentResolvers(argumentResolvers);
    }

    protected List<MethodArgumentResolver> getDefaultArgumentResolvers() {
        // 通常是处理特定类型与特定注解的解析器放置在前面，以便优先利用它来解析参数
        List<MethodArgumentResolver> argumentResolvers = new ArrayList<>();
        argumentResolvers.add(new ServletApiMethodArgumentResolver());
        argumentResolvers.add(new FileTypeMethodArgumentResolver());
        // RequestBody解析器要放在bean解析器之前，Simple与Bean类型解析器应该放在最后
        argumentResolvers.add(new RequestBodyMethodArgumentResolver());
        // PathVariable解析器应该放置在Simple解析器之前
        argumentResolvers.add(new PathVariableMethodArgumentResolver());
        argumentResolvers.add(new SimpleTypeMethodArgumentResolver());
        argumentResolvers.add(new BeanMethodArgumentResolver());

        return argumentResolvers;
    }

    protected List<MethodArgumentResolver> getCustomArgumentResolvers() {
        return MvcContext.getMvcContext().getCustomArgumentResolvers();
    }

    private void initHandlerMappings() {
        // 优先添加用户自定义的HandlerMapping
        List<HandlerMapping> customHandlerMappings = getCustomHandlerMappings();
        // mvc框架自身的HandlerMapping优先级更低，后注册
        List<HandlerMapping> defaultHandlerMappings = getDefaultHandlerMappings();

        handlerMappings.addAll(customHandlerMappings);
        handlerMappings.addAll(defaultHandlerMappings);
        // 把定制+默认的所有HandlerMapping组件添加到上下文中
        MvcContext.getMvcContext().setHandlerMappings(handlerMappings);
    }

    protected List<HandlerMapping> getCustomHandlerMappings() {
        return MvcContext.getMvcContext().getCustomHandlerMappings();
    }

    protected List<HandlerMapping> getDefaultHandlerMappings() {
        List<HandlerMapping> mappings = new ArrayList<>();
        mappings.add(new RequestMappingHandlerMapping());
        mappings.add(new NameConventionHandlerMapping());
        return mappings;
    }

    private void initHandlerAdapters() {
        List<HandlerAdapter> customHandlerAdapters = getCustomHandlerAdapters();
        List<HandlerAdapter> defaultHandlerAdapters = getDefaultHandlerAdapters();

        handlerAdapters.addAll(customHandlerAdapters);
        handlerAdapters.addAll(defaultHandlerAdapters);
        MvcContext.getMvcContext().setHandlerAdapters(handlerAdapters);
    }

    protected List<HandlerAdapter> getCustomHandlerAdapters() {
        return MvcContext.getMvcContext().getCustomHandlerAdapters();
    }

    protected List<HandlerAdapter> getDefaultHandlerAdapters() {
        List<HandlerAdapter> adapters = new ArrayList<>();
        adapters.add(new RequestMappingHandlerAdapter());
        adapters.add(new HttpRequestHandlerAdapter());
        // handlerAdapters.add(new MethodNameHandlerAdapter());
        return adapters;
    }

    private void initExceptionResolvers() {
        List<HandlerExceptionResolver> customExceptionResolvers = getCustomExceptionResolvers();
        List<HandlerExceptionResolver> defaultExceptionResolvers = getDefaultExceptionResolvers();
        exceptionResolvers.addAll(customExceptionResolvers);
        exceptionResolvers.addAll(defaultExceptionResolvers);
        MvcContext.getMvcContext().setExceptionResolvers(exceptionResolvers);
    }

    protected List<HandlerExceptionResolver> getCustomExceptionResolvers() {
        return MvcContext.getMvcContext().getCustomExceptionResolvers();
    }

    protected List<HandlerExceptionResolver> getDefaultExceptionResolvers() {
        List<HandlerExceptionResolver> resolvers = new ArrayList<>();
        resolvers.add(new LogHandlerExceptionResolver());
        resolvers.add(new PrintStackTraceHandlerExceptionResolver());
        resolvers.add(new ExceptionHandlerExceptionResolver());
        resolvers.add(new ParameterizedExceptionHandlersExceptionResolver());
        return resolvers;
    }

    protected String[] getBasePackages(ServletConfig config) {
        String pkg = config.getInitParameter(BASE_PACKAGE);
        if (pkg == null || pkg.isEmpty()) {
            throw new IllegalStateException("必须指定扫描的包，此包是控制器、拦截器或者是其它Mvc框架扩展组件所在的包");
        }
        return StringUtils.split(pkg, Delimiters.COMMON.getPattern())
                .toArray(new String[]{});
    }

    // endregion
    // region 请求处理逻辑

    /**
     * 方法通常会包含很多核心逻辑步骤，如果每一个逻辑步骤都有一些零散的代码
     * 如果都放在一起，就会导致本方法代码很长，不容易看懂。
     * 所以，最好是把其中一个个的核心逻辑用单独的方法封装起来
     * <p>
     * service的方法，由于是重写父类型的方法，其签名是不应该改变的，
     * 比如改成throws Throwable，这是不行的.所以就增加了一个doService的方法，
     * 以便有机会改doService的签名,让其不抛出异常，因为doService方法处理了异常
     * </p>
     * <p>增加了一个doService方法也让service逻辑更清晰，次要的跨域处理逻辑放在service方法里，
     * 重要的请求处理逻辑放在doService方法里</p>
     *
     * @param req  请求对象
     * @param resp 响应对象
     * @throws ServletException Servlet异常
     * @throws IOException      IO异常
     */
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        setEncoding(req, resp);
        // 如果进行跨域处理之后不需要再进行后续处理（比如预检请求），直接返回，不继续后面的请求处理流程
        if (!processCors(req, resp, corsConfiguration)) {
            return;
        }

        doService(req, resp);
    }

    /**
     * 此方法是真正的请求处理方法，核心的任务有：
     * <ol>
     *     <li>处理HandlerContext</li>
     *     <li>利用HandlerMapping找到HandlerExecutionChain</li>
     *     <li>由doDispatch去处理链的执行</li>
     *     <li>由noHandlerFound去处理找不到Handler（也就没有链）的情况</li>
     * </ol>
     * <p>
     *     注意：HandlerMapping查找Handler的过程中出现的异常不会被HandlerExceptionResolver去处理
     *     doDispatch方法里才会利用异常解析器处理执行链的异常
     * </p>
     *
     * @param req  请求对象
     * @param resp 响应对象
     */
    protected void doService(HttpServletRequest req, HttpServletResponse resp) {
        HandlerContext context = HandlerContext.getHandlerContext();
        context.request(req).response(resp);
        try {
            HandlerExecutionChain chain = getHandler(req);
            if (chain != null) {
                doDispatch(req, resp, chain);
            } else {
                noHandlerFound(req, resp);
            }
        } catch (Throwable ex) {
            processThrowable(req, resp, ex);
        } finally {
            // 保存到ThreadLocal的内容一定要清掉，所以放在finally是合理的
            context.close();
        }
    }

    /**
     * 此方法完成了链的执行和视图结果的渲染
     *
     * @param req   请求对象
     * @param resp  响应对象
     * @param chain 执行链
     * @throws Throwable 整个请求处理过程中可能出现的异常
     */
    protected void doDispatch(HttpServletRequest req, HttpServletResponse resp, HandlerExecutionChain chain) throws Throwable {
        ViewResult viewResult;
        try {
            // 这里返回false，执行完拦截器的后置逻辑后直接return，结束后续流程
            if (!chain.applyPreHandle(req, resp)) {
                chain.applyPostHandle(req, resp);
                return;
            }
            viewResult = applyHandle(req, resp, chain.getHandler());
            chain.applyPostHandle(req, resp);
        } catch (Exception ex) {
            // 这里只处理Exception，非Exception并没有处理，会继续抛出给doService处理.
            viewResult = resolveException(req, resp, chain.getHandler(), ex);

           /* // 如果拦截器的前置代码或者handler的执行出了异常，拦截器的前置逻辑可能已经执行了，那么拦截器的后置逻辑要不要执行呢？
            // 如果选择要执行，那么就加上下面的这行代码，但加了后，后置逻辑执行中出异常是没有被解析器处理的
            // 如果要处理，就不能仅仅只加下面的这一行代码就解决问题了。
            // 如果也给其添加异常处理，那么就会导致有2个ViewResult对象，一个是后置代码的异常处理ViewResult对象,
            // 另一个是前置代码和handler异常处理ViewResult，这样就会存在2个选1个的问题
            // 基于这些考虑，所以最终选择前置逻辑与handler执行出了异常，不执行后置逻辑
            // chain.applyPostHandle(req, resp);*/

        }
        render(req, resp, viewResult);
    }

    protected ViewResult applyHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {
        HandlerAdapter adapter = getHandlerAdapter(handler);
        return adapter.handle(req, resp, handler);
    }

    protected ViewResult resolveException(HttpServletRequest req, HttpServletResponse resp, Object handler, Exception ex) throws Exception {
        // 在解析异常时是可能抛出异常的，有可能是异常解析器方法本身执行过程中出异常了
        // 抛出的异常会交给doService方法的catch块处理，而不会是doDispatch方法的catch块处理
        for (HandlerExceptionResolver exceptionResolver : exceptionResolvers) {
            ViewResult result = exceptionResolver.resolveException(req, resp, handler, ex);
            if (result != null) {
                return result;
            }
        }
        // 表示没有一个异常解析器可以处理异常，那么就应该把异常继续抛出,会交给doService方法去处理，因而也不会进行渲染处理
        throw ex;
    }

    /**
     * 这里是对视图结果进行渲染处理，主要是通过调用{@link ViewResult#render(HttpServletRequest, HttpServletResponse)}实现的，具体逻辑见各个ViewResult的子类
     * <p>这里不需要对viewResult进行null的判断，因为
     * <ul>
     *     <li>如果执行链能正常执行，那么viewResult不为null</li>
     *     <li>如果执行链执行异常，但异常解析器能正确的解析，那么异常解析器会返回一个不为空的ViewResult对象</li>
     *     <li>如果执行链异常，异常解析器也"不能"处理异常，那么{@link #resolveException(HttpServletRequest, HttpServletResponse, Object, Exception)}方法会抛出此异常，
     *     导致本方法不会得到调用</li>
     * </ul>
     * 总之，render方法能执行，viewResult就不会为null,render处理的viewResult可能是handler返回的也可能是异常解析器处理异常之后返回的viewResult</p>
     * <p><b><i>注意：签名为Exception的方法是可能抛出{@link Error}异常的</i></b>，因为Error异常用法上类似于RuntimeException，
     * 不需要在方法签名的throws里添加Error异常，重要的是不需要catch Error异常，详情见{@link Error}类的源码注释,比如下面的代码
     * <pre class="code">
     *    static void m1() throws Exception {
     *         System.out.println("m1----");
     *         throw new StackOverflowError("overflow");
     *     }
     * </pre>
     * </p>
     *
     * @param req        请求对象
     * @param resp       响应对象
     * @param viewResult 视图结果
     * @throws Exception 渲染时可能抛出的异常
     */
    protected void render(HttpServletRequest req, HttpServletResponse resp, ViewResult viewResult) throws Exception {
        viewResult.render(req, resp);
    }

    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        for (HandlerMapping mapping : handlerMappings) {
            HandlerExecutionChain chain = mapping.getHandler(request);
            if (chain != null) {
                return chain;
            }
        }
        return null;
    }

    /**
     * 设置编码的方法是在service方法里面第一个调用，如果已经从req
     * 对象中获取数据了，再设置这个编码是无效的
     *
     * @param req  请求对象
     * @param resp 响应对象
     * @throws IOException 设置编码时可能抛出的IOException
     */
    protected void setEncoding(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        req.setCharacterEncoding("UTF-8");
        resp.setCharacterEncoding("UTF-8");
    }

    /**
     * 处理未找到handler的情况，通过默认servlet来处理
     * <p>默认servlet也可以处理静态资源</p>
     * <p>不是所有的容器的默认servlet名字都叫default，常见的tomcat与jetty容器是叫这个名字，所以下面的代码是有隐患的，
     * 各个容器的默认servlet获取逻辑见spring mvc的DefaultServletHttpRequestHandler类</p>
     *
     * @param req  请求对象
     * @param resp 响应对象
     * @throws Exception 默认servlet处理时抛出的异常
     */
    protected void noHandlerFound(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        // resp.sendError(HttpServletResponse.SC_NOT_FOUND);
        req.getServletContext().getNamedDispatcher("default").forward(req, resp);
    }

    /**
     * 处理整个请求过程中出现的异常，不是执行链执行过程中的异常,这里捕获的ex可能是
     * <ul>
     *     <li>HandlerMapping查找Handler过程中出现的异常</li>
     *     <li>执行链执行过程中抛出的异常（没有任何异常解析器能处理时)或者拦截器后置逻辑执行时出现的异常</li>
     *     <li>某个异常解析器处理执行链的异常时异常解析器自己本身抛出了异常</li>
     *     <li>{@link #noHandlerFound(HttpServletRequest, HttpServletResponse)}抛出的异常</li>
     *     <li>视图渲染时出现了异常</li>
     * </ul>
     *
     * @param req  请求对象
     * @param resp 响应对象
     * @param ex   请求处理过程中出现的异常
     */
    protected void processThrowable(HttpServletRequest req, HttpServletResponse resp, Throwable ex) {
        MvcConfigurer mvcConfigurer = MvcContext.getMvcContext().getMvcConfigurer();
        if (mvcConfigurer != null) {
            mvcConfigurer.processBeyondChainException(req, resp, ex);
        } else {
            processBeyondChainException(req, resp, ex);
        }
    }

    /**
     * 可以在这里处理执行链之外的异常，执行链以外的异常通常就是视图渲染与异常解析器执行过程中出现的异常
     * <p><i>注意</i>：这里的ex通常是视图渲染时出现的异常，也可能是执行链执行过程中产生的异常，比如没有异常解析器能处理执行链的异常、
     * 执行链抛出的异常不是Exception的子类或者拦截器的后置逻辑执行过程中产生的异常等，这种情况我把其称之为执行链之外要处理的异常</p>
     *
     * @param ex 异常对象
     */
    protected void processBeyondChainException(HttpServletRequest req, HttpServletResponse resp, Throwable ex) {
        System.out.println("==================Handler执行链之外的异常信息(begin)=====================");
        System.out.println(ex.getMessage());
        System.out.println("==================Handler执行链之外的异常信息(end)=======================");
        resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
    }

    protected HandlerAdapter getHandlerAdapter(Object handler) throws Exception {
        for (HandlerAdapter adapter : handlerAdapters) {
            if (adapter.supports(handler)) {
                return adapter;
            }
        }
        throw new ServletException("此Handler没有对应的adapter去处理，请在DispatcherServlet中进行额外的配置");
    }

    protected CorsProcessor getCorsProcessor() {
        return new DefaultCorsProcessor();
    }

    protected boolean processCors(HttpServletRequest req, HttpServletResponse resp, CorsConfiguration config) throws IOException {
        return getCorsProcessor().processCors(req, resp, config);
    }
    // endregion
}
